<?php if(!defined('ARCH_ROOT_PATH')) exit;
/**
 *	Architect Framework
 *
 *	Architect Framework is a light weight object oriented web applications framework written for PHP 5.1.4 and later that provide the tools to develop and deploy a website or application fast and with as little code as possible.
 *
 *	@author Robin Grass, <hello@coffinboy.se>
 *	@link http://architect.kodlabbet.net
 *	@license http://www.opensource.org/licenses/gpl-3.0.html GNU General Public License (v. 3)
 */

/**
 *	Architect_Router
 *
 *	Architect_Router parses a URI and calls the right controller and that controller's method based on route map.
 *
 *	@package Runtime
 *	@version 1.0
 *	@author Robin Grass, <hello@coffinboy.se>
 */
class Architect_Router {

	/**
	 *	@var object $uri
	 */
	protected $uri;
	
	/**
	 *	@var array $routes
	 */
	protected $routes;
	
	/**
	 *	@var array $request
	 */
	protected $request;
	
	/**
	 *	@var string $default_contoller
	 */
	protected $default_contoller;
	
	/**
	 *	@var bool $is_error
	 */
	protected $is_error = false;
	
	/**
	 *	@var array $wildcards
	 */
	protected $wildcards = array(
		':num' => '([0-9]+)',
		':str' => '([+-_a-zA-Z0-9]+)',
		':word' => '([a-zA-Z]+)'
	);

	/**
	 *	__construct
	 *
	 *	This constructor sets class members.
	 *
	 *	@param object Architect_URI::$uri
	 *	@param array $routes
	 */
	public function __construct(Architect_URI $uri, $routes) {
		$this->uri = $uri;
		$this->routes = $routes;
		$this->default_controller = strtolower($routes['default_controller']);
		
		$this->parseRoutes();
	}
	
	/**
	 *	setWildcard
	 *
	 *	This method sets or adds a new wildcard to be used in the route maps.
	 *
	 *	@param string $wildcard
	 *	@param string $regex
	 */
	public function setWildcard($wildcard, $regex) {
		$this->wildcards[$wildcard] = $regex;
	}
	
	/**
	 *	setRequest
	 *
	 *	This method sets the request array.
	 *
	 *	@param string $class
	 *	@param string $method
	 *	@param array $params
	 */
	protected function setRequest($class = null, $method = null, $params = array()) {
		$this->request = array(
			'class' => ($class) ? $class : $this->default_controller,
			'method' => ($method) ? $method : (($this->is_error) ? 'error' : 'index'),
			'params' => $params
		);
	}
	
	/**
	 *	getRequest
	 *
	 *	This method returns the request array.
	 *
	 *	@return array
	 */
	public function getRequest() {
		return $this->request;
	}
	
	/**
	 *	getClass
	 *
	 *	This method gets the class from the first URI segment.
	 *
	 *	@return string
	 */
	protected function getClass() {
		return ($this->uri->getSegment(1)) ? $this->uri->getSegment(1) : $this->default_controller;
	}
	
	/**
	 *	getMethod
	 *
	 *	This method gets the method from the second URI segment.
	 *
	 *	@return string
	 */
	protected function getMethod() {
		return ($this->uri->getSegment(2)) ? $this->uri->getSegment(2) : 'index';
	}
	
	/**
	 *	parseRoutes
	 *
	 *	This method parses the routes array, and replaces wildcards into their corresponding regex.
	 *
	 *	@return void
	 */
	protected function parseRoutes() {
		$virtual = $this->uri->getVirtual();
		foreach($this->routes as $key => $route) {
			// Replace wildcards into their corresponding regexes.
			$wildcards = array_keys($this->wildcards);
			$wildcards_regex = array_values($this->wildcards);
			$key = str_ireplace($wildcards, $wildcards_regex, $key);
			
			// Checks if the regex has a match.
			if(preg_match('#^' . $key . '$#', $virtual)) {
				// If there's a backreference, set it
				if(strpos($route, '$') !== false && strpos($key, '(') !== false)
					$route = preg_replace('#^' . $key . '$#', $route, $virtual);
				
				// Set the request to a matching route map
				$_route = explode('/', trim($route, '/'));
				$params = array_slice($_route, 2);
				$this->setRequest($_route[0], (array_key_exists(1, $_route)) ? $_route[1] : null, $params);
			return;
			}
		}
		
		// If no route map matches were found, assume controller and method from URI segments.
		$this->setRequest($this->getClass(), $this->getMethod());
	return;
	}
	
	/**
	 *	loadRequestFile
	 *
	 *	This method attempts to load the requested controller file, loads default controller on failure.
	 *
	 *	@param string $class
	 */
	protected function loadRequestFile($class) {
		$request_controller = ARCH_CONTROLLERS_PATH . strtolower($class) . ARCH_FILE_EXT;
		$default_controller = ARCH_CONTROLLERS_PATH . $this->default_controller . ARCH_FILE_EXT;
		if(file_exists($request_controller))
			require_once $request_controller;
		else {
			$this->is_error = true;
			require_once $default_controller;
		}
	}
	
	/**
	 *	createInstance
	 *
	 *	This method creates a new instance of a controller, if no such controller exists, it creates a new instance of the default controller.
	 *
	 *	@param string $class
	 */
	protected function createInstance($class) {
		$request_controller = ucfirst($class) . '_Controller';
		$default_controller = ucfirst($this->default_controller) . '_Controller';
		if(class_exists($request_controller))
			$controller = $request_controller;
		else $controller = $default_controller;
	
	return call_user_func_array(array(new ReflectionClass($controller), 'newInstance'), array());
	}
	
	/**
	 *	delegate
	 *
	 *	This method handles the delegation of the parsed route and request.
	 */
	public function delegate() {
		// Load the request and requested files and create a new instance.
		$request = $this->getRequest();		
		$this->loadRequestFile($request['class']);
		$instance = $this->createInstance($request['class']);
		
		if(!$this->is_error) {
			// Attempt to call class' method.
			if(method_exists($instance, $request['method']))
				call_user_func_array(array($instance, $request['method']), $request['params']);
			else call_user_func_array(array($instance, 'error'));
		} else {
			// There's an error, call it and reset Architect_Router::$is_error.
			call_user_func_array(array($instance, 'error'));
			$this->is_error = false;
		}
	}

}
?>